/***************************************************************************
 *   This is a part of the NextCAD Web APP.                                *
 *   Copyright (C) AMCAX, All rights reserved.                             *
 ***************************************************************************/

import {
  CENTEREVENTS,
  EventsRes,
  Matrix3x4,
  SceneSetup,
  Transform,
} from '@amcax/base';
import { eventCenterManager } from '@app-base/common/events/EventCenterManager';
import { KEYBOARD_KEY } from '@app-cad/common/Constants';
import { ctx2, userEvents } from '@app-cad/module';
import { Matrix4, Vector3 } from 'three-legacy';

enum Step {
  MOVESTART = 'moveStart',
  MOVEEND = 'moveEnd',
}

export class ModelMover {
  private static _instance: ModelMover;

  private models: Array<any>;
  private sceneSetup: SceneSetup;
  private startPoint: Vector3;
  private endPoint: Vector3;
  private step: Step;

  private matrix: Matrix4; // 偏移矩阵
  private previousInverseMatrix: Matrix4; // 上次的偏移矩阵的逆
  private abortMatrix: Matrix4; // 上次的偏移矩阵
  private finalMatrix: Matrix4; // 最终偏移矩阵
  private position: Vector3; // 初始坐标
  private previousPosition: Vector3; // 上次偏移后的坐标
  private deltaPosition: Vector3; // 偏移量
  // private csys: CSys; // 初始csys
  private deltaOrigin: Vector3; // 偏移origin
  private csysMatrix: Matrix4; // transform偏移矩阵

  public static getInstance(): ModelMover {
    if (!this._instance) {
      this._instance = new ModelMover();
    }
    return this._instance;
  }

  private constructor() {
    this.init();
  }

  private init() {
    this.sceneSetup = ctx2.viewer.sceneSetup;
    this.startPoint = null;
    this.endPoint = null;
    this.step = Step.MOVESTART;

    this.models = [];
    this.matrix = new Matrix4();
    this.previousInverseMatrix = new Matrix4();
    this.abortMatrix = new Matrix4();
    this.finalMatrix = new Matrix4();
    this.csysMatrix = new Matrix4();
    this.deltaOrigin = new Vector3();
    this.position = new Vector3();
    // this.previousPosition = new Vector3();
    this.deltaPosition = new Vector3();
  }

  abort() {
    this.startPoint = null;
    this.endPoint = null;
    this.step = Step.MOVESTART;
    this.position = new Vector3();
    this.previousPosition = new Vector3();
    this.applyMatrix4(this.abortMatrix.multiply(this.previousInverseMatrix));
    this.matrix = new Matrix4();
    this.previousInverseMatrix = new Matrix4();
    this.abortMatrix = new Matrix4();
    ctx2.viewer.requestRender();
  }

  private registerEvents() {
    eventCenterManager.activeEventCenter
      .on(CENTEREVENTS.POINTERUP, this.pointerUp)
      .on(CENTEREVENTS.POINTERMOVE, this.pointerMove)
      .addExit(this.exit);
    document.addEventListener('keyup', this.keyUp);
  }

  private unregisterEvents() {
    eventCenterManager.activeEventCenter
      .off(CENTEREVENTS.POINTERUP, this.pointerUp)
      .off(CENTEREVENTS.POINTERMOVE, this.pointerMove)
      .setDefault();
    document.removeEventListener('keyup', this.keyUp);
  }

  public startMove(model: any, startPoint: Vector3) {
    this.models = [];
    this.models.push(model.children[0]);
    this.models.push(model.children[1]);
    this.registerEvents();
    this.startPoint = startPoint;
    this.step = Step.MOVEEND;
  }

  private pointerUp = (e: EventsRes) => {
    if (!e.button.onLeft) {
      return;
    }
    if (this.step === Step.MOVESTART) {
      const point = this.sceneSetup.mouseOnPlane(e.mouse.x, e.mouse.y);
      this.startPoint = new Vector3(point.x, point.y, point.z);
      this.step = Step.MOVEEND;
    } else if (this.step === Step.MOVEEND) {
      this.commitTransform();
      this.unregisterEvents();
    }
  };
  private pointerMove = (e: EventsRes) => {
    const point = this.sceneSetup.mouseOnPlane(e.mouse.x, e.mouse.y);
    this.endPoint = new Vector3(point.x, point.y, point.z);
    if (this.startPoint) {
      this.calculateTransform();
    }
  };
  private keyUp = (e: KeyboardEvent) => {
    if (e.key === KEYBOARD_KEY.SPACE || e.key === KEYBOARD_KEY.ENTER) {
      if (this.step === Step.MOVEEND) {
        this.commitTransform();
      }
    } else if (e.key === KEYBOARD_KEY.ESC) {
      this.exit();
    }
  };

  private calculateTransform() {
    const moveDir = this.endPoint
      .clone()
      .sub(this.startPoint.clone())
      .normalize();
    const distance = this.startPoint.clone().distanceTo(this.endPoint.clone());
    // 计算偏移矩阵并应用
    this.deltaPosition = moveDir
      .clone()
      .setLength(distance)
      .add(this.position.clone());
    // this.previousPosition = this.deltaPosition.clone();
    if (distance > 1000000) {
      return;
    }
    this.matrix = new Matrix4().makeTranslation(
      this.deltaPosition.x,
      this.deltaPosition.y,
      this.deltaPosition.z,
    );
    this.finalMatrix = this.matrix
      .clone()
      .multiply(this.previousInverseMatrix.clone());
    this.applyMatrix4(this.finalMatrix);
    this.previousInverseMatrix = this.matrix.clone().invert();
    // 计算transform偏移矩阵
    this.deltaOrigin = moveDir.clone().setLength(distance);
    this.csysMatrix = new Matrix4().makeTranslation(
      this.deltaOrigin.x,
      this.deltaOrigin.y,
      this.deltaOrigin.z,
    );
  }

  private commitTransform() {
    const placement = Transform.newTransFormMatrix(
      new Matrix3x4().setFromMatrix4(this.csysMatrix),
    );
    userEvents.emit('componentMoveEnd', { placement });
  }

  private applyMatrix4(matrix: Matrix4) {
    for (let i = 0, l = this.models.length; i < l; i++) {
      this.models[i].transferByMatrix4(matrix);
    }
    ctx2.viewer.requestRender();
  }

  public exit() {
    this.unregisterEvents();
    this.abort();
  }
}

export const modelMover = ModelMover.getInstance();
